﻿using S7.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Zhy.S7NETDemo;

namespace Zhy.S7NETDemo
{
    /// <summary>
    /// PLC通信帮助类 - 基于S7netplus库封装
    /// </summary>
    public class S7netplusHelper : IDisposable
    {
        private Plc _plc;
        private bool _isConnected = false;
        private Timer _heartbeatTimer;
        private const int HEARTBEAT_INTERVAL = 5000; // 5秒心跳间隔
        private readonly object _lockObject = new object();

        /// <summary>
        /// 连接状态变更事件
        /// </summary>
        public event Action<bool> ConnectionStatusChanged;

        /// <summary>
        /// 通信异常事件
        /// </summary>
        public event Action<Exception> CommunicationError;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="cpuType">PLC CPU类型</param>
        /// <param name="ip">PLC IP地址</param>
        /// <param name="rack">机架号</param>
        /// <param name="slot">槽号</param>
        public S7netplusHelper(CpuType cpuType, string ip, short rack = 0, short slot = 0)
        {
            _plc = new Plc(cpuType, ip, rack, slot);
            _heartbeatTimer = new Timer(HeartbeatCallback, null, Timeout.Infinite, HEARTBEAT_INTERVAL);
        }

        /// <summary>
        /// 连接到PLC
        /// </summary>
        /// <param name="timeoutMs">连接超时时间(毫秒)</param>
        /// <returns>连接是否成功</returns>
        public bool Connect(int timeoutMs = 5000)
        {
            lock (_lockObject)
            {
                try
                {
                    _plc.Open();
                    _isConnected = _plc.IsConnected;

                    if (_isConnected)
                    {
                        _heartbeatTimer.Change(0, HEARTBEAT_INTERVAL);
                        ConnectionStatusChanged?.Invoke(true);
                        return true;
                    }
                }
                catch (Exception ex)
                {
                    _isConnected = false;
                    CommunicationError?.Invoke(ex);
                    return false;
                }
            }
            return false;
        }

        /// <summary>
        /// 异步连接到PLC
        /// </summary>
        /// <param name="timeoutMs">连接超时时间(毫秒)</param>
        /// <returns>连接任务</returns>
        public async Task<bool> ConnectAsync(int timeoutMs = 5000)
        {
            return await Task.Run(() => Connect(timeoutMs));
        }

        /// <summary>
        /// 断开与PLC的连接
        /// </summary>
        public void Disconnect()
        {
            lock (_lockObject)
            {
                try
                {
                    _heartbeatTimer.Change(Timeout.Infinite, Timeout.Infinite);
                    _plc.Close();
                    _isConnected = false;
                    ConnectionStatusChanged?.Invoke(false);
                }
                catch (Exception ex)
                {
                    CommunicationError?.Invoke(ex);
                }
            }
        }

        /// <summary>
        /// 读取字节数据
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <param name="count">读取字节数</param>
        /// <returns>读取的字节数组</returns>
        public byte[] ReadBytes(DataType dataType, int db, int startByte, int count)
        {
            CheckConnection();

            try
            {
                return _plc.ReadBytes(dataType, db, startByte, count);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"读取字节数据失败 - DB{db}.DBB{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 写入字节数据
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <param name="data">要写入的字节数组</param>
        public void WriteBytes(DataType dataType, int db, int startByte, byte[] data)
        {
            CheckConnection();

            try
            {
                _plc.WriteBytes(dataType, db, startByte, data);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"写入字节数据失败 - DB{db}.DBB{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 读取单个布尔值
        /// </summary>
        /// <param name="address">PLC地址 (如 "DB1.DBX0.0", "M10.1", "I0.0", "Q0.0")</param>
        /// <returns>读取到的布尔值</returns>
        public bool ReadBool(string address)
        {
            CheckConnection();

            try
            {
                object result = _plc.Read(address);

                if (result is bool boolValue)
                {
                    return boolValue;
                }
                else
                {
                    throw new Exception($"读取地址 {address} 失败，返回类型不是布尔值");
                }
            }
            catch (Exception ex)
            {
                throw new Exception($"读取地址 {address} 时发生错误: {ex.Message}");
            }
        }

        /// <summary>
        /// 使用第二种方式读取DB区布尔值
        /// </summary>
        /// <param name="dbNumber">DB块号</param>
        /// <param name="byteOffset">字节偏移量</param>
        /// <param name="bitOffset">位偏移量 (0-7)</param>
        /// <returns>读取到的布尔值</returns>
        public bool ReadDBBool(int dbNumber, int byteOffset, byte bitOffset)
        {
            CheckConnection();

            if (bitOffset < 0 || bitOffset > 7)
            {
                throw new ArgumentException("位偏移量必须在0-7范围内");
            }

            try
            {
                // 使用DataType.DataBlock方式读取
                var result = _plc.Read(DataType.DataBlock, dbNumber, byteOffset, VarType.Bit, 1, bitOffset);

                if (result is bool[] boolArray && boolArray.Length > 0)
                {
                    return boolArray[0];
                }
                else
                {
                    throw new Exception($"读取DB{dbNumber}.DBX{byteOffset}.{bitOffset} 失败");
                }
            }
            catch (Exception ex)
            {
                throw new Exception($"读取DB区布尔值时发生错误: {ex.Message}");
            }
        }

        /// <summary>
        /// 写入单个布尔值
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <param name="bit">位地址(0-7)</param>
        /// <param name="value">要写入的布尔值</param>
        public void WriteBool(DataType dataType, int db, int startByte, byte bit, bool value)
        {
            CheckConnection();

            try
            {
                _plc.WriteBit(dataType, db, startByte, bit, value);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"写入布尔值失败 - {dataType}{db}.{startByte}.{bit}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 读取整数(16位)
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <returns>读取的整数值</returns>
        public short ReadInt(DataType dataType, int db, int startByte)
        {
            CheckConnection();

            try
            {
                var bytes = _plc.ReadBytes(dataType, db, startByte, 2);
                return (short)((bytes[0] << 8) | bytes[1]);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"读取整数失败 - DB{db}.DBW{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 写入整数(16位)
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <param name="value">要写入的整数值</param>
        public void WriteInt(DataType dataType, int db, int startByte, short value)
        {
            CheckConnection();

            try
            {
                var bytes = new byte[2];
                bytes[0] = (byte)((value >> 8) & 0xFF);
                bytes[1] = (byte)(value & 0xFF);
                _plc.WriteBytes(dataType, db, startByte, bytes);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"写入整数失败 - DB{db}.DBW{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 读取双整数(32位)
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <returns>读取的双整数值</returns>
        public int ReadDInt(DataType dataType, int db, int startByte)
        {
            CheckConnection();

            try
            {
                var bytes = _plc.ReadBytes(dataType, db, startByte, 4);
                return S7.Net.Types.DInt.FromByteArray(bytes);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"读取双整数失败 - DB{db}.DBD{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 写入双整数(32位)
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <param name="value">要写入的双整数值</param>
        public void WriteDInt(DataType dataType, int db, int startByte, int value)
        {
            CheckConnection();

            try
            {
                var bytes = S7.Net.Types.DInt.ToByteArray(value);
                _plc.WriteBytes(dataType, db, startByte, bytes);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"写入双整数失败 - DB{db}.DBD{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 读取实数(32位浮点数)
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <returns>读取的浮点数值</returns>
        public float ReadReal(DataType dataType, int db, int startByte)
        {
            CheckConnection();

            try
            {
                var bytes = _plc.ReadBytes(dataType, db, startByte, 4);
                return S7.Net.Types.Real.FromByteArray(bytes);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"读取实数失败 - DB{db}.DBD{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 写入实数(32位浮点数)
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <param name="value">要写入的浮点数值</param>
        public void WriteReal(DataType dataType, int db, int startByte, float value)
        {
            CheckConnection();

            try
            {
                var bytes = S7.Net.Types.Real.ToByteArray(value);
                _plc.WriteBytes(dataType, db, startByte, bytes);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"写入实数失败 - DB{db}.DBD{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 读取字符串
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <param name="length">字符串长度</param>
        /// <returns>读取的字符串</returns>
        public string ReadString(DataType dataType, int db, int startByte, int length)
        {
            CheckConnection();

            try
            {
                var bytes = _plc.ReadBytes(dataType, db, startByte, length + 2); // +2 for header
                return S7.Net.Types.String.FromByteArray(bytes);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"读取字符串失败 - DB{db}.DBS{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 写入字符串
        /// </summary>
        /// <param name="dataType">数据类型</param>
        /// <param name="db">数据块编号</param>
        /// <param name="startByte">起始字节地址</param>
        /// <param name="value">要写入的字符串</param>
        /// <param name="length">字符串长度</param>
        public void WriteString(DataType dataType, int db, int startByte, string value, int length)
        {
            CheckConnection();

            try
            {
                var bytes = S7.Net.Types.String.ToByteArray(value, length);
                _plc.WriteBytes(dataType, db, startByte, bytes);
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"写入字符串失败 - DB{db}.DBS{startByte}: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 批量读取多个数据项
        /// </summary>
        /// <param name="readItems">读取项列表</param>
        /// <returns>读取结果字典</returns>
        public Dictionary<string, object> BatchRead(List<PlcReadItem> readItems)
        {
            CheckConnection();

            var results = new Dictionary<string, object>();
            try
            {
                foreach (var item in readItems)
                {
                    switch (item.DataType)
                    {
                        case PlcDataType.Bool:
                            results[item.Name] = ReadDBBool(item.DB, item.StartByte, item.Bit);
                            break;
                        case PlcDataType.Byte:
                            results[item.Name] = ReadBytes(item.Type, item.DB, item.StartByte, 1)[0];
                            break;
                        case PlcDataType.Int:
                            results[item.Name] = ReadInt(item.Type, item.DB, item.StartByte);
                            break;
                        case PlcDataType.DInt:
                            results[item.Name] = ReadDInt(item.Type, item.DB, item.StartByte);
                            break;
                        case PlcDataType.Real:
                            results[item.Name] = ReadReal(item.Type, item.DB, item.StartByte);
                            break;
                        case PlcDataType.String:
                            results[item.Name] = ReadString(item.Type, item.DB, item.StartByte, item.Length);
                            break;
                    }
                }
                return results;
            }
            catch (Exception ex)
            {
                throw new PlcCommunicationException($"批量读取失败: {ex.Message}", ex);
            }
        }

        /// <summary>
        /// 检查连接状态
        /// </summary>
        private void CheckConnection()
        {
            if (!_isConnected || !_plc.IsConnected)
            {
                throw new PlcCommunicationException("PLC未连接，请先建立连接");
            }
        }

        /// <summary>
        /// 心跳回调函数
        /// </summary>
        private void HeartbeatCallback(object state)
        {
            try
            {
                if (_isConnected && !_plc.IsConnected)
                {
                    _isConnected = false;
                    ConnectionStatusChanged?.Invoke(false);
                }
            }
            catch
            {
                // 忽略心跳检查中的异常
            }
        }

        /// <summary>
        /// 获取连接状态
        /// </summary>
        public bool IsConnected => _isConnected && _plc.IsConnected;

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            _heartbeatTimer?.Dispose();
            _plc?.Close();
            _isConnected = false;
        }
    }

    /// <summary>
    /// PLC读取项定义
    /// </summary>
    public class PlcReadItem
    {
        public string Name { get; set; }
        public DataType Type { get; set; }
        public int DB { get; set; }
        public int StartByte { get; set; }
        public byte Bit { get; set; }
        public int Length { get; set; }
        public PlcDataType DataType { get; set; }
    }

    /// <summary>
    /// PLC数据类型枚举
    /// </summary>
    public enum PlcDataType
    {
        Bool,
        Byte,
        Int,
        DInt,
        Real,
        String
    }

    /// <summary>
    /// PLC通信异常类
    /// </summary>
    public class PlcCommunicationException : Exception
    {
        public PlcCommunicationException(string message) : base(message) { }
        public PlcCommunicationException(string message, Exception innerException) : base(message, innerException) { }
    }
}
